using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using MacomberMap.Common.Components;
using System.Drawing;
using MacomberMap.Common.Database_Interface.CIM.Components;
using System.Windows.Forms;
using MacomberMap.Common.User_Interfaces.One_Lines.Elements;

namespace MacomberMap.Common.Database_Interface.CIM.One_Line_Interface
{
    /// <summary>
    /// This class holds information on a parsed DDL XML element
    /// </summary>
    public class MM_OneLine_XmlComponent
    {
        #region Variable declarations

        /// <summary>The element at the core of this DDL Component</summary>
        public XmlElement BaseElement;

        /// <summary>The rectangle of this element</summary>
        public Rectangle ElementRectangle;

        /// <summary>The rectangle of the connected elements</summary>
        public Rectangle DescriptorRectangle;

        /// <summary>The CIM element associated with the present element</summary>
        public CIM_Element AssociatedElement;

        /// <summary>Whether this element has yet been processed</summary>
        public bool Processed = false;

        /// <summary>The parent of this component, if any</summary>
        public MM_OneLine_XmlComponent Parent = null;

        /// <summary>Subelements under the current one</summary>
        public List<MM_OneLine_XmlComponent> SubElements;

        /// <summary>
        /// Retrieve the name of an item
        /// </summary>
        public string Name
        {
            get
            {
                if (BaseElement.HasAttribute("Name"))
                    return BaseElement.Attributes["Name"].Value;
                else if (BaseElement.HasAttribute("gab"))
                    return BaseElement.Attributes["gab"].Value;
                else
                    return "?";
            }
        }

        /// <summary>
        /// Retrieve the data key of an item (if any)
        /// </summary>
        public String Key
        {
            get
            {
                XmlElement CompositeKey = BaseElement["composite_key"];
                if (CompositeKey != null)
                {
                    StringBuilder outStr = new StringBuilder();
                    string[] spl1 = CompositeKey.Attributes["record_key"].Value.Split(',');
                    string[] spl2 = CompositeKey.Attributes["record"].Value.Split(',');
                    for (int a = 0; a < spl1.Length; a++)
                        outStr.Append((outStr.Length > 0 ? "," : "") + spl2[a] + "=" + spl1[a]);
                    return outStr.ToString();
                }
                else if (BaseElement.HasAttribute("Value"))
                    return BaseElement.Attributes["Value"].Value;
                else
                    return null;
            }
        }

        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new XML Component
        /// </summary>
        /// <param name="BaseElement">The element around which this component is based</param>
        /// <param name="ESet">The ESET data</param>
        public MM_OneLine_XmlComponent(XmlElement BaseElement, XmlDocument ESet)
        {
            IntegrateElement(BaseElement, ESet);
            this.BaseElement = BaseElement;
            this.ElementRectangle = BuildElementRectangle(BaseElement);
        }

        /// <summary>
        /// Initialize a new Xml component based on its children.
        /// </summary>
        /// <param name="BaseElement">The transformer winding on which the component is based</param>
        public MM_OneLine_XmlComponent(MM_OneLine_XmlComponent BaseElement)
        {
            ElementRectangle = BaseElement.ElementRectangle;
            this.BaseElement = BaseElement.BaseElement;
        }
        #endregion

        #region XML Merging
        /// <summary>
        /// Recursively go through the current node, and pull in picture, symbol, gab, etc., information in 
        /// </summary>
        /// <param name="ElemToIntegrate">The element to be integrated</param>
        /// <param name="ESet">The Element set information</param>
        private void IntegrateElement(XmlElement ElemToIntegrate, XmlDocument ESet)
        {
            //Import our picture information and such from the ESET
            if (ElemToIntegrate.Name == "picture")
                MergeXml(ElemToIntegrate, ESet.SelectSingleNode("DDL/picture[@Name='" + ElemToIntegrate.Attributes["Name"].Value + "']") as XmlElement);

            //Now, go through everything, and import our symbols
            if (ElemToIntegrate.Name == "symbol")
                MergeXml(ElemToIntegrate, ESet.SelectSingleNode("DDL/symbol[@Name='" + ElemToIntegrate.Attributes["Name"].Value + "']") as XmlElement);

            //Now, go through everything, and roll in our cams.
            if (ElemToIntegrate.HasAttribute("cam"))
                MergeXml(ElemToIntegrate, ESet.SelectSingleNode("DDL/cam[@Name='" + ElemToIntegrate.Attributes["cam"].Value + "']") as XmlElement);

            //Now, go through everything, and roll in our gabs.
            if (ElemToIntegrate.HasAttribute("gab"))
                MergeXml(ElemToIntegrate, ESet.SelectSingleNode("DDL/gab[@Name='" + ElemToIntegrate.Attributes["gab"].Value + "']") as XmlElement);

            //Now, do the same for all child nodes.
            foreach (XmlElement xE2 in ElemToIntegrate.ChildNodes)
                IntegrateElement(xE2, ESet);
        }

        /// <summary>
        /// Import attributes and elements from an eset node (attributes and sub-elements) into a dset element
        /// </summary>
        /// <param name="DSetElement">The DSet element (target)</param>
        /// <param name="ESetElement">The ESet element (source)</param>
        private void MergeXml(XmlElement DSetElement, XmlElement ESetElement)
        {
            foreach (XmlAttribute xAttr in ESetElement.Attributes)
                if (xAttr.Name != "Name")
                    DSetElement.Attributes.Append(DSetElement.OwnerDocument.ImportNode(xAttr, false) as XmlAttribute);
            foreach (XmlElement xElem2 in ESetElement.ChildNodes)
                DSetElement.AppendChild(DSetElement.OwnerDocument.ImportNode(xElem2, true));
        }
        #endregion

        #region Rectangle building
        /// <summary>
        /// Build an element's rectangle
        /// </summary>
        /// <param name="BaseElement"></param>
        /// <returns></returns>
        public Rectangle BuildElementRectangle(XmlElement BaseElement)
        {
            //Initialize our parameters
            Point StartPoint = Point.Empty;
            Point EndPoint = Point.Empty;
            int LineWidth = 1;

            //If we have a line width, update it
            if (BaseElement.HasAttribute("line_width"))
                LineWidth = int.Parse(BaseElement.Attributes["line_width"].Value);

            //If we have an origin, update it.
            if (BaseElement.HasAttribute("origin"))
            {
                String[] splStr = BaseElement.Attributes["origin"].Value.Split(',');
                StartPoint = EndPoint = new Point(int.Parse(splStr[0]) - (LineWidth / 2), int.Parse(splStr[1]) - (LineWidth / 2));
            }

            //If we have a specified rectangle, handle accordingly.
            if (BaseElement.Name == "rectangle")
            {
                EndPoint.X += int.Parse(BaseElement.Attributes["width"].Value);
                EndPoint.Y += int.Parse(BaseElement.Attributes["height"].Value);
            }

            //If we have a formatted field, pull out our font information and determine its size
            if (BaseElement.Name == "formatted_field" || BaseElement.Name == "text")
            {
                //Measure our formatted field
                String TextToMeasure = (BaseElement.Name == "formatted_field" ? new string(BaseElement.Attributes["show_char"].Value[0], int.Parse(BaseElement.Attributes["size"].Value) + (BaseElement.HasAttribute("sign_textpair") && XmlConvert.ToBoolean(BaseElement.Attributes["sign_textpair"].Value) ? 1 : 0)) : BaseElement.Attributes["Value"].Value);
                String FontFamily = (BaseElement.HasAttribute("font_family") ? BaseElement.Attributes["font_family"].Value : "Courier");
                float FontSize = (BaseElement.HasAttribute("font_size") ? XmlConvert.ToSingle(BaseElement.Attributes["font_size"].Value) : 8);
                using (Font TextFont = new Font(FontFamily, FontSize))
                using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
                    EndPoint = Point.Add(EndPoint, Size.Ceiling(g.MeasureString(TextToMeasure, TextFont)));
            }

            //If we have a collection of points, update them
            if (BaseElement.HasAttribute("point"))
            {
                String[] splStr = BaseElement.Attributes["point"].Value.Split(',');
                Point ThisPt = new Point(int.Parse(splStr[0]), int.Parse(splStr[1]));// - (LineWidth / 2), int.Parse(splStr[1]) - LineWidth / 2);
                for (int a = 0; a < splStr.Length; a += 2)
                {
                    if (a > 0)
                    {
                        ThisPt.X += int.Parse(splStr[a]);
                        ThisPt.Y += int.Parse(splStr[a + 1]);
                    }
                    if (ThisPt.X < StartPoint.X)
                        StartPoint.X = ThisPt.X;
                    if (ThisPt.Y < StartPoint.Y)
                        StartPoint.Y = ThisPt.Y;
                    if (ThisPt.X > EndPoint.X)
                        EndPoint.X = ThisPt.X;
                    if (ThisPt.Y > EndPoint.Y)
                        EndPoint.Y = ThisPt.Y;
                }

                int Adj = (int)Math.Ceiling((float)LineWidth / 2f);
                StartPoint.X -= Adj;
                StartPoint.Y -= Adj;
                EndPoint.X += Adj;
                EndPoint.Y += Adj;

            }

            //If we're a line, update the width appropriate
            if (BaseElement.Name == "polyline")
            {
                EndPoint.X += LineWidth - 1;
                EndPoint.Y += LineWidth - 1;
            }



            //Build our outgoing rectangle
            Rectangle OutRect = Rectangle.FromLTRB(StartPoint.X, StartPoint.Y, EndPoint.X, EndPoint.Y);


            //Now, go through any child nodes and update the rectangles 
            foreach (XmlElement ChildNode in BaseElement.ChildNodes)
                if (ChildNode.Name != "picture")
                {
                    Rectangle InRect = BuildElementRectangle(ChildNode);
                    InRect.X += OutRect.Left;
                    InRect.Y += OutRect.Top;
                    if (InRect.Right > OutRect.Right)
                        OutRect.Width = InRect.Right - OutRect.Left;
                    if (InRect.Bottom > OutRect.Bottom)
                        OutRect.Height = InRect.Bottom - OutRect.Top;

                    //OutRect = DetermineOutermostBounds(InRect, OutRect);                
                }
            return OutRect;
        }
        #endregion

        #region Element merging routines
        /// <summary>
        /// Search to find all elements of a specified name or gab around an element in question
        /// </summary>        
        /// <param name="BaseElement">Our base element to be matched around</param>
        /// <param name="Components">The collection of components</param>
        /// <param name="PropigateOutward">Whether propigation occurs from one acceptible element to the next</param>        
        /// <param name="OuterBounds">The outermost boundaries of the elements</param>
        /// <param name="NamesToFind">The names to be located</param>
        /// <returns></returns>
        private static Dictionary<MM_OneLine_XmlComponent, CIM_Element> LocateAdjacentElements(MM_OneLine_XmlComponent BaseElement, Dictionary<MM_OneLine_XmlComponent, CIM_Element> Components, bool PropigateOutward, out Rectangle OuterBounds, params String[] NamesToFind)
        {
            //Start off by building a rectangle corresponding to this element's information
            Rectangle BaseRect = BaseElement.ElementRectangle;
            OuterBounds = Rectangle.Empty;
            Dictionary<MM_OneLine_XmlComponent, CIM_Element> OutElems = new Dictionary<MM_OneLine_XmlComponent, CIM_Element>(20);

            //Build our rectangles for all other potentially matching items
            Dictionary<MM_OneLine_XmlComponent, Rectangle> PotentialRects = new Dictionary<MM_OneLine_XmlComponent, Rectangle>(20);
            foreach (MM_OneLine_XmlComponent ElemToCheck in Components.Keys)
                if (ElemToCheck != BaseElement && !ElemToCheck.Processed && Array.IndexOf<String>(NamesToFind, ElemToCheck.Name) != -1)
                    PotentialRects.Add(ElemToCheck, ElemToCheck.ElementRectangle);

            //Now, pull in all elements close to the base
            foreach (KeyValuePair<MM_OneLine_XmlComponent, Rectangle> RectToTest in PotentialRects)
                if (CheckAdjacency(BaseRect, RectToTest.Value))
                {
                    OuterBounds = DetermineOutermostBounds(OuterBounds, RectToTest.Value);
                    OutElems.Add(RectToTest.Key, RectToTest.Key.AssociatedElement);
                }

            //If we're not propigating further, stop here.
            if (!PropigateOutward)
                return OutElems;

            //Otherwise, keep checking for adjacent elements until we find none
            int NumEntries;
            Dictionary<MM_OneLine_XmlComponent, CIM_Element> ToAdd = new Dictionary<MM_OneLine_XmlComponent, CIM_Element>(PotentialRects.Count);
            do
            {
                NumEntries = 0;
                ToAdd.Clear();
                foreach (MM_OneLine_XmlComponent ElemToCheck in OutElems.Keys)
                    foreach (KeyValuePair<MM_OneLine_XmlComponent, Rectangle> RectToTest in PotentialRects)
                        if (RectToTest.Key != ElemToCheck && !OutElems.ContainsKey(RectToTest.Key) && CheckAdjacency(PotentialRects[ElemToCheck], RectToTest.Value))
                        {
                            ToAdd.Add(RectToTest.Key, RectToTest.Key.AssociatedElement);
                            OuterBounds = DetermineOutermostBounds(OuterBounds, RectToTest.Value);
                            NumEntries++;
                        }

                //Now, move everything from our add list out
                foreach (KeyValuePair<MM_OneLine_XmlComponent, CIM_Element> ElemToAdd in ToAdd)
                    OutElems.Add(ElemToAdd.Key, ElemToAdd.Value);

            } while (NumEntries > 0);

            return OutElems;
        }


        /// <summary>
        /// Check to see whether two rectangles are adjacent
        /// </summary>
        /// <param name="BaseRect">The base rectangle</param>
        /// <param name="SecondRect">The rectangle to be matched against</param>
        /// <returns></returns>
        private static bool CheckAdjacency(Rectangle BaseRect, Rectangle SecondRect)
        {
            //Determine if our rectangles overlap within a reasonable range
            int FudgeFactor = 10;
            for (int BaseX = BaseRect.X; BaseX <= BaseRect.Right; BaseX++)
                for (int BaseY = BaseRect.Y; BaseY <= BaseRect.Bottom; BaseY++)
                    for (int SecondX = SecondRect.X; SecondX <= SecondRect.Right; SecondX++)
                        for (int SecondY = SecondRect.Y; SecondY <= SecondRect.Bottom; SecondY++)
                            if (Math.Abs(BaseX - SecondX) <= FudgeFactor && Math.Abs(BaseY - SecondY) <= FudgeFactor)
                                return true;

            return false;
        }

        /// <summary>
        /// Determine the outermost points on the rectangle
        /// </summary>
        /// <param name="RectanglesToCheck">The rectangles to be checked</param>
        /// <returns></returns>
        private static Rectangle DetermineOutermostBounds(params Rectangle[] RectanglesToCheck)
        {
            if (RectanglesToCheck.Length == 0)
                return Rectangle.Empty;
            else
            {
                //First, find a non-empty rectangle to start with
                Rectangle OutRect = Rectangle.Empty;
                foreach (Rectangle ToTest in RectanglesToCheck)
                    if (!ToTest.IsEmpty)
                    {
                        OutRect = ToTest;
                        break;
                    }

                foreach (Rectangle ToTest in RectanglesToCheck)
                    if (!ToTest.IsEmpty)
                    {
                        OutRect.X = Math.Min(OutRect.X, ToTest.X);
                        OutRect.Y = Math.Min(OutRect.Y, ToTest.Y);
                    }

                //Now, do the same for the right and bottom points as the maxima
                foreach (Rectangle ToTest in RectanglesToCheck)
                    if (!ToTest.IsEmpty)
                    {
                        if (ToTest.Right > OutRect.Right)
                            OutRect.Width = ToTest.Right - OutRect.Left;
                        if (ToTest.Bottom > OutRect.Bottom)
                            OutRect.Height = ToTest.Bottom - OutRect.Top;
                    }
                return OutRect;
            }
        }
        #endregion

        #region Debugging information
        /// <summary>
        /// View the element's data linkages
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            String Name = this.Name;
            if (Key != null)
                Name += " (" + Key + ")";
            if (this.AssociatedElement != null && this.AssociatedElement.VoltageLevel != null)
                return String.Format("{0} ({1} {2}; {3},{4},{5},{6})", Name, AssociatedElement.VoltageLevel.Name + " " + AssociatedElement.Type.Split(':')[1], AssociatedElement.Name, ElementRectangle.X, ElementRectangle.Y, ElementRectangle.Right, ElementRectangle.Bottom);
            else if (this.AssociatedElement != null)
                return String.Format("{0} ({1} {2}; {3},{4},{5},{6})", Name, AssociatedElement.Type.Split(':')[1], AssociatedElement.Name, ElementRectangle.X, ElementRectangle.Y, ElementRectangle.Right, ElementRectangle.Bottom);
            else if (this.Parent != null)
                return String.Format("{0} ({1},{2}) - ({3},{4}): Parent = {5}", Name, ElementRectangle.X, ElementRectangle.Y, ElementRectangle.Right, ElementRectangle.Bottom, this.Parent.ToString());
            else
                return String.Format("{0} ({1},{2}) - ({3},{4})", Name, ElementRectangle.X, ElementRectangle.Y, ElementRectangle.Right, ElementRectangle.Bottom);



        }
        #endregion

        #region Component integration
        /// <summary>
        /// Pull in all components around the current component
        /// </summary>        
        /// <param name="Components">The current collection of components</param>
        public void IntegrateComponents(Dictionary<MM_OneLine_XmlComponent, CIM_Element> Components)
        {
            Rectangle OutRect;
            Dictionary<MM_OneLine_XmlComponent, CIM_Element> AdjacentElements;

            //Go through each potential type, and pull in the relevant information
            if (this.Name.StartsWith("LINE_"))
                AdjacentElements = LocateAdjacentElements(this, Components, true, out OutRect, "ANALOG_N_LN_U", "ANALOG_N_LN_D", "ANALOG_N_LN_L", "ANALOG_N_LN_R", "ANALOG_SUBSTN_NET", "TEXT_FV_CHR_NONENTRY_SIMPLE_ERCOT");
            else if (this.Name.StartsWith("CB_NET_") || this.Name.StartsWith("SW_NET_"))
                AdjacentElements = LocateAdjacentElements(this, Components, true, out OutRect, "CB_SW_NET_LABEL");
            else if (this.Name.StartsWith("LOAD_"))
                AdjacentElements = LocateAdjacentElements(this, Components, true, out OutRect, "ANALOG_N_LD_U", "ANALOG_N_LD_D", "ANALOG_N_LD_L", "ANALOG_N_LD_R", "TEXT_FV_CHR_NONENTRY_SIMPLE_ERCOT");
            else if (this.Name.StartsWith("CAPACITOR_") || this.Name.StartsWith("REACTOR_"))
                AdjacentElements = LocateAdjacentElements(this, Components, true, out OutRect, "TEXT_FV_CHR_NONENTRY_SIMPLE_ERCOT");
            else if (this.Name.StartsWith("GEN_NET_"))
                AdjacentElements = LocateAdjacentElements(this, Components, true, out OutRect, "ANALOG_N_UN_U", "ANALOG_N_UN_D", "ANALOG_N_UN_L", "ANALOG_N_UN_R", "GEN_LABEL_NET");
            else if (this.Name.EndsWith("_BUS"))
                AdjacentElements = LocateAdjacentElements(this, Components, true, out OutRect, "KV_NET_LABEL", "KV_NETWORK");
            else
                return;

            //Now, build our list of potential elements
            List<CIM_Element> MatchedElem = new List<CIM_Element>(AdjacentElements.Count);
            foreach (CIM_Element Elem in AdjacentElements.Values)
                if (Elem != null && !MatchedElem.Contains(Elem))
                    MatchedElem.Add(Elem);

            //If we have just one, let's process and remove all of the potentials that match our intended one, and flag appropriately.
            List<MM_OneLine_XmlComponent> OutArray = new List<MM_OneLine_XmlComponent>(10);
            if (MatchedElem.Count == 1)
            {
                this.AssociatedElement = MatchedElem[0];
                foreach (KeyValuePair<MM_OneLine_XmlComponent, CIM_Element> kvp in AdjacentElements)
                    if (kvp.Value == this.AssociatedElement)
                    {
                        OutArray.Add(kvp.Key);
                        Components.Remove(kvp.Key);
                        Components[this] = kvp.Value;
                    }
                    else if (kvp.Key.Name.StartsWith("CONN_") || this.Name.EndsWith("_BUS"))
                    {
                        OutArray.Add(kvp.Key);
                        Components.Remove(kvp.Key);
                    }
                (this.SubElements = OutArray).TrimExcess();
            }
        }

        /// <summary>
        /// Pull all components around the node into it, stopping when we hit elements
        /// </summary>
        /// <param name="Components"></param>
        public void IntegrateNode(Dictionary<MM_OneLine_XmlComponent, CIM_Element> Components)
        {
            List<MM_OneLine_XmlComponent> NodeComponents = new List<MM_OneLine_XmlComponent>();
            ParseNodeComponents(Components, this, NodeComponents);

            //Now, pull together the list of all CIM elements contained within if we don't have it already
            int CountElements = 0;
            if (this.AssociatedElement == null)
            {
                //Build our list of nodes and the count we find
                Dictionary<CIM_Element, int> NodeMatches = new Dictionary<CIM_Element, int>();
                foreach (MM_OneLine_XmlComponent Elem in NodeComponents)
                    if (Elem.AssociatedElement != null)
                    {
                        CountElements++;
                        foreach (CIM_Element Node in Elem.AssociatedElement.Nodes)
                            if (NodeMatches.ContainsKey(Node))
                                NodeMatches[Node]++;
                            else
                                NodeMatches.Add(Node, 1);
                    }

                //Now, if we find a proper match, handle accordingly.
                foreach (KeyValuePair<CIM_Element, int> kvp in NodeMatches)
                    if (kvp.Value == CountElements && AssociatedElement == null)
                        AssociatedElement = kvp.Key;
                    else if (kvp.Value == CountElements)
                        throw new InvalidOperationException("Located multiple possiblities for this node match!");

                //If we couldn't find one, throw an error
                if (AssociatedElement == null)
                    throw new InvalidOperationException("Unable to find a node pair match!");
            }

            //Now, roll in all our points into the root element. If we have other points, make them ours.
            if (this.SubElements == null)
                this.SubElements = new List<MM_OneLine_XmlComponent>();
            foreach (MM_OneLine_XmlComponent InElem in NodeComponents)
            {
                this.SubElements.Add(InElem);
                if (InElem.AssociatedElement == null)
                {
                    InElem.AssociatedElement = this.AssociatedElement;
                    Components.Remove(InElem);
                }
            }
            this.SubElements.TrimExcess();


        }

        /// <summary>
        /// Parse around a node component to locate adjacent components
        /// </summary>
        /// <param name="Components">The full set of components from the DDL</param>
        /// <param name="NodeBase">The base for this comparison</param>
        /// <param name="NodeComponents">The accumulating list of node components</param>
        private void ParseNodeComponents(Dictionary<MM_OneLine_XmlComponent, CIM_Element> Components, MM_OneLine_XmlComponent NodeBase, List<MM_OneLine_XmlComponent> NodeComponents)
        {
            NodeComponents.Add(NodeBase);
            foreach (KeyValuePair<MM_OneLine_XmlComponent, CIM_Element> kvp in Components)
                //If we have an already-associated element, stop parsing here
                if (kvp.Key != NodeBase && !NodeComponents.Contains(kvp.Key) && !kvp.Key.Name.EndsWith("_LABEL") && !kvp.Key.Name.EndsWith("_NETWORK"))
                    if (CheckAdjacency(NodeBase.ElementRectangle, kvp.Key.ElementRectangle))
                        if (!NodeComponents.Contains(kvp.Key))
                            if (kvp.Key.AssociatedElement != null)
                                NodeComponents.Add(kvp.Key);
                            else
                                ParseNodeComponents(Components, kvp.Key, NodeComponents);
        }

        /// <summary>
        /// Determine if this component can be integrated
        /// </summary>
        public bool CanIntegrate
        {
            get
            {
                if (this.Name.StartsWith("LINE_"))
                    return true;
                else if (this.Name.StartsWith("CB_NET_") || this.Name.StartsWith("SW_NET_"))
                    return true;
                else if (this.Name.StartsWith("LOAD_"))
                    return true;
                else if (this.Name.StartsWith("CAPACITOR_") || this.Name.StartsWith("REACTOR_"))
                    return true;
                else if (this.Name.StartsWith("GEN_NET_"))
                    return true;
                else if (this.Name.EndsWith("_BUS"))
                    return true;
                else
                    return false;
            }
        }
        #endregion

        #region Xml Exporting
        /// <summary>
        /// Create an XML representation of the current element
        /// </summary>
        /// <param name="ParentDocument"></param>
        /// <returns></returns>
        public XmlElement CreateXml(XmlDocument ParentDocument)
        {
            //Create our outgoing element
            XmlElement OutElement;
            if (AssociatedElement == null)
            {
                OutElement = ParentDocument.CreateElement("Unlinked_Element");
                OutElement.Attributes.Append(ParentDocument.CreateAttribute("Type")).Value = Internals.MM_Repository.TitleCase(BaseElement.Name);
                foreach (XmlAttribute xAttr in this.BaseElement.Attributes)
                    OutElement.Attributes.Append(ParentDocument.CreateAttribute(xAttr.Name)).Value = xAttr.Value;
            }
            else
            {
                OutElement = ParentDocument.CreateElement(AssociatedElement.Type.Split(':')[1]);
                OutElement.Attributes.Append(ParentDocument.CreateAttribute("TEID")).Value = AssociatedElement.TEID;
                OutElement.Attributes.Append(ParentDocument.CreateAttribute("rdfID")).Value = AssociatedElement.rdfID.ToString();
                OutElement.Attributes.Append(ParentDocument.CreateAttribute("Name")).Value = AssociatedElement.Name;
                if (AssociatedElement.VoltageLevel != null)
                    OutElement.Attributes.Append(ParentDocument.CreateAttribute("KVLevel")).Value = AssociatedElement.VoltageLevel.Name;

                //If we're a node, add in our list of elements from CIM
                if (AssociatedElement.Type == "cim:ConnectivityNode" || AssociatedElement.Type == "cim:BusbarSection")
                {
                    XmlAttribute OutElems = ParentDocument.CreateAttribute("Elements");
                    foreach (CIM_Element Elem in AssociatedElement.Elements)
                        if (Elem != this.AssociatedElement)
                            if (Elem.Type == "cim:ACLineSegment" || Elem.Type == "cim:SeriesCompensator")
                                OutElems.Value += (OutElems.Value.Length > 0 ? "," + Elem.Parse("cim:Line").TEID : Elem.Parse("cim:Line").TEID);
                            else
                                OutElems.Value += (OutElems.Value.Length > 0 ? "," + Elem.TEID : Elem.TEID);
                    OutElement.Attributes.Append(OutElems);
                    if (AssociatedElement.Type == "cim:BusbarSection")
                    {
                        OutElement.Attributes.Append(ParentDocument.CreateAttribute("NodeTEID")).Value = AssociatedElement.Parse("cim:Terminal>cim:ConnectivityNode").TEID;
                        OutElement.Attributes.Append(ParentDocument.CreateAttribute("NodeRdfID")).Value = AssociatedElement.Parse("cim:Terminal>cim:ConnectivityNode").rdfID.ToString();
                    }

                }
                //Otherwise, add in our nodes.
                else
                {
                    XmlAttribute OutNodes = ParentDocument.CreateAttribute("Nodes");
                    foreach (CIM_Element Node in AssociatedElement.Nodes)
                        if (Node.Parse("cim:Terminal>cim:BusbarSection") != null)
                            OutNodes.Value += (OutNodes.Value.Length > 0 ? "," : "") + Node.Parse("cim:Terminal>cim:BusbarSection").TEID;
                        else
                            OutNodes.Value += (OutNodes.Value.Length > 0 ? "," : "") + Node.TEID;
                    OutElement.Attributes.Append(OutNodes);
                }

                //If we have a line, add our substation list too
                if (AssociatedElement.Type == "cim:Line")
                {
                    XmlAttribute OutStations = ParentDocument.CreateAttribute("Stations");
                    foreach (CIM_Element Station in AssociatedElement.ACSegmentStations)
                        OutStations.Value += (OutStations.Value.Length > 0 ? "," : "") + Station.TEID;
                    OutElement.Attributes.Append(OutStations);
                }
            }

            //Determine our orientation and sub-element orientations, if possible
            if (ParseOrientation(this.Name) != "Unknown")
                OutElement.Attributes.Append(ParentDocument.CreateAttribute("Orientation")).Value = ParseOrientation(Name);

            //Go through all sub-elements looking for orientation and whether they should be included in our rectangle.
            if (this.SubElements != null)
            {
                List<Rectangle> Descriptors = new List<Rectangle>(this.SubElements.Count);
                foreach (MM_OneLine_XmlComponent SubComponent in this.SubElements)
                    if (SubComponent.AssociatedElement == this.AssociatedElement && SubComponent != this && !SubComponent.Name.StartsWith("CONN_") && !SubComponent.Name.EndsWith("_BUS") && !SubComponent.Name.EndsWith("_LINES"))
                    {
                        Descriptors.Add(SubComponent.ElementRectangle);
                        if (ParseOrientation(SubComponent.Name) != "Unknown")
                            OutElement.Attributes.Append(ParentDocument.CreateAttribute("DescriptorOrientation")).Value = ParseOrientation(Name);
                    }
                DescriptorRectangle = MM_OneLine_XmlComponent.DetermineOutermostBounds(Descriptors.ToArray());
            }

            //If we're a connectivity node or bus, locate each element, and find the closest connectivity node around it.
            if (this.Name.StartsWith("CONN_") || this.Name.EndsWith("_BUS"))
            {
                XmlAttribute ConnectionPoints = ParentDocument.CreateAttribute("ConnectionPoints");
                foreach (MM_OneLine_XmlComponent ConnPoint in SubElements)
                    if (ConnPoint.CanIntegrate && ConnPoint.AssociatedElement != this.AssociatedElement)
                    {
                        MM_OneLine_XmlComponent ClosestNode = LocateClosestNode(ConnPoint);
                        ConnectionPoints.Value += (ConnectionPoints.Value.Length > 0 ? "," : "") + ConnPoint.AssociatedElement.TEID + "," + ClosestNode.ElementRectangle.X + "," + ClosestNode.ElementRectangle.Y;
                    }
                OutElement.Attributes.Append(ConnectionPoints);
            }


            //Write out our bounds and sub-element bounds.
            OutElement.Attributes.Append(ParentDocument.CreateAttribute("Bounds")).Value = RectangleText(this.ElementRectangle);
            if (!DescriptorRectangle.IsEmpty)
                OutElement.Attributes.Append(ParentDocument.CreateAttribute("DescriptorBounds")).Value = RectangleText(this.DescriptorRectangle);

            //If we're a capacitor/reactor, write out our type
            if (AssociatedElement != null && AssociatedElement.Type == "cim:ShuntCompensator")
                if (float.Parse(AssociatedElement["cim:ShuntCompensator.nominalMVAr"]) > 0)
                    OutElement.Attributes.Append(ParentDocument.CreateAttribute("Type")).Value = "Capacitor";
                else
                    OutElement.Attributes.Append(ParentDocument.CreateAttribute("Type")).Value = "Reactor";






            return OutElement;
        }

        /// <summary>
        /// Locate the closest viable node point in proximity to the associated element
        /// </summary>
        /// <param name="ConnectionPoint"></param>
        /// <returns></returns>
        private MM_OneLine_XmlComponent LocateClosestNode(MM_OneLine_XmlComponent ConnectionPoint)
        {
            MM_OneLine_XmlComponent OutElement = null;
            double Dist = double.NaN;
            Point CenterPt = CenterPoint(ConnectionPoint.ElementRectangle);
            foreach (MM_OneLine_XmlComponent Component in SubElements)
                if (Component.Name.StartsWith("CONN_"))
                {
                    Point CenterPt2 = CenterPoint(Component.ElementRectangle);
                    double Dist2 = (float)Math.Sqrt(Math.Pow(CenterPt.X - CenterPt2.X, 2) + Math.Pow(CenterPt.Y - CenterPt2.Y, 2));
                    if (OutElement == null || Dist2 < Dist)
                    {
                        Dist = Dist2;
                        OutElement = Component;
                    }
                }
            return OutElement;
        }

        /// <summary>
        /// Return the center point of a rectangle
        /// </summary>
        /// <param name="InRect">The rectangle to center on</param>
        /// <returns></returns>
        private Point CenterPoint(Rectangle InRect)
        {
            return new Point(InRect.Left + (InRect.Width / 2), InRect.Top + (InRect.Height / 2));
        }

        /// <summary>
        /// Return a rectangle in string form
        /// </summary>
        /// <param name="OutRect"></param>
        /// <returns></returns>
        private String RectangleText(Rectangle OutRect)
        {
            return OutRect.Left + "," + OutRect.Top + "," + OutRect.Width + "," + OutRect.Height;
        }

        /// <summary>
        /// Determine the orientation of an element
        /// </summary>
        /// <param name="Name">The name of the element from which orientation is derived</param>
        /// <returns></returns>
        private string ParseOrientation(string Name)
        {
            if (Name.EndsWith("_V"))
                return "Vertical";
            else if (Name.EndsWith("_H"))
                return "Horizontal";
            else if (Name.EndsWith("_R"))
                return "Right";
            else if (Name.EndsWith("_L"))
                return "Left";
            else if (Name.EndsWith("_U"))
                return "Up";
            else if (Name.EndsWith("_D"))
                return "Down";
            else
                return "Unknown";

        }
        #endregion

        #region Type determination
        /// <summary>
        /// Return the intended CIM type of our element
        /// </summary>
        public String CIMType
        {
            get
            {
                if (this.Name.StartsWith("CB_SW_") && this.Key.Contains("CB="))
                    return "cim:Breaker";
                else if (this.Name.StartsWith("CB_NET"))
                    return "cim:Breaker";
                else if (this.Name.StartsWith("SW_NET"))
                    return "cim:Disconnector";
                throw new InvalidOperationException("Unable to determine element type on the generic component!");
            }
        }

        /// <summary>
        /// Return whether the element is flagged as a node.
        /// </summary>
        public bool IsNode
        {
            get { return Name.EndsWith("_LINES") || Name.StartsWith("CONN_") || Name.StartsWith("JUMPER_"); }
        }
        #endregion


        #region Point derivation
        /// <summary>
        /// Retrieve the points for the item in question
        /// </summary>
        public Point[] Points
        {
            get
            {
                List<Point> OutPoints = new List<Point>();
                //If we have a collection of points, update them
                if (BaseElement.HasAttribute("point"))
                {
                    String[] splStr = BaseElement.Attributes["point"].Value.Split(',');
                    Point ThisPt = Point.Empty;
                    for (int a = 0; a < splStr.Length; a += 2)
                    {
                        ThisPt.X += int.Parse(splStr[a]);
                        ThisPt.Y += int.Parse(splStr[a + 1]);
                        OutPoints.Add(ThisPt);
                    }
                }
                return OutPoints.ToArray();
            }
        }


        /// <summary>
        /// Draw the component on the graphics space
        /// </summary>
        /// <param name="e"></param>
        /// <param name="Sender"></param>
        public void DrawComponent(PaintEventArgs e, MM_OneLine_Element Sender)
        {
            if (this.Parent != null)
            {
                e.Graphics.FillRectangle(Brushes.LightBlue, e.ClipRectangle);
                if (Sender is MM_OneLine_UnlinkedElement && !String.IsNullOrEmpty(Key))
                    e.Graphics.DrawString(Key.Substring(Key.LastIndexOf(',') + 1), Sender.Font, Brushes.Black, Sender.DisplayRectangle, MM_OneLine_Element.CenterFormat);
            }
            else if (Sender is MM_OneLine_UnlinkedElement && Points.Length > 0)
            {
                List<Point> OutPoint = new List<Point>();
                foreach (Point pt in Points)
                    OutPoint.Add(new Point(pt.X - ElementRectangle.Left, pt.Y - ElementRectangle.Y));
                e.Graphics.DrawLines(Pens.DarkGray, OutPoint.ToArray());
            }
            else if (Sender is MM_OneLine_UnlinkedElement)
            {
                e.Graphics.FillRectangle(Brushes.DarkGray, e.ClipRectangle);
                if (!String.IsNullOrEmpty(Key))
                    e.Graphics.DrawString(Key.Substring(Key.LastIndexOf(',') + 1), Sender.Font, Brushes.Black, Sender.DisplayRectangle, MM_OneLine_Element.CenterFormat);
            }
            else if (Sender is MM_OneLine_Descriptor)
            {
                e.Graphics.FillRectangle(Brushes.DarkBlue, e.ClipRectangle);
                Sender.DrawImage(e.Graphics);
            }
            else if (Sender is MM_OneLine_PricingVector)
            {
                e.Graphics.FillRectangle(((Sender as MM_OneLine_PricingVector).BaseNode == null ? Brushes.Red : Brushes.DarkGreen), e.ClipRectangle);
                Sender.DrawImage(e.Graphics);
            }
            else
            {
                e.Graphics.FillRectangle((Sender.BaseElement == null ? Brushes.Red : Brushes.DarkGreen), e.ClipRectangle);
                //Sender.DrawImage(e.Graphics);
            }
        }
        #endregion
    }
}
